import { computed, getCurrentInstance, isVue2, ComputedRef } from 'vue-demi'

// @ts-ignore
import type { RawLocation, Route } from 'vue-router'
import {
  Component,
  ErrorHandler,
  Location,
  NavigationGuard,
  RouteConfig,
  RouteRecordPublic,
} from 'vue-router/types/router'

interface Resolve {
  location: Location
  route: Route
  href: string
  // backwards compat
  normalizedTo: Location
  resolved: Route
}

interface RouterHook {
  currentRoute: ComputedRef<Route>

  push(location: RawLocation): Promise<any>

  replace(location: RawLocation): Promise<any>

  hasRoute(name: string): void

  isReady(): Promise<any>

  beforeEach(guard: NavigationGuard): any

  beforeResolve(guard: NavigationGuard): any

  afterEach(hook: (to: Route, from: Route) => any): any

  go(n: number): void

  back(): void

  forward(): void

  match(raw: RawLocation, current?: Route, redirectedFrom?: Location): Route

  getMatchedComponents(to?: RawLocation | Route): Component[]

  onReady(cb: () => any, errorCb?: ErrorHandler): void

  onError(cb: ErrorHandler): void

  addRoutes(routes: RouteConfig[]): void

  addRoute(parent: string, route: RouteConfig): void

  addRoute(route: RouteConfig): void

  getRoutes(): RouteRecordPublic[]

  resolve(to: RawLocation, current?: Route, append?: boolean): Resolve
}

export const useRouter = (): RouterHook => {
  if (!isVue2) {
    return new Error('该hook只能在vue2环境使用') as any as RouterHook
  }
  /**
   * 获取路由实例
   */
  // @ts-ignore
  const router = getCurrentInstance()?.proxy?.$router as any

  /**
   * 路由信息
   */
  const currentRoute = computed<Route>(() => router.currentRoute)

  /**
   * 新的路由
   * @param {*} location
   * @returns
   */
  const push = (location: RawLocation) =>
    new Promise<void>((resolve, reject) => {
      router.push(
        location,
        () => resolve(),
        (e) => reject(e)
      )
    })

  /**
   * 重定向
   * @param {*} location
   * @returns
   */
  const replace = (location: RawLocation) =>
    new Promise<void>((resolve, reject) => {
      router.replace(
        location,
        () => resolve(undefined),
        (e) => reject(e)
      )
    })

  /**
   * 前进或回退页面栈
   * @param {*} n
   * @returns
   */
  const go = (n) => router.go(n)

  /**
   * 后退
   * @returns
   */
  const back = () => router.back()

  /**
   * 路由重载
   * @returns
   */
  const forward = () => router.forward()

  /**
   * 获取路由实例
   * @param location
   * @param current
   * @param append
   */
  const resolve = (location: RawLocation, current?: Route, append?: boolean): Resolve =>
    router.resolve(location, current, append)

  /**
   * 是否存在该路由
   * @param name
   */
  const hasRoute = (name: string): boolean => !!router.match(name)

  const match = (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route =>
    router.match(raw, current, redirectedFrom)

  const beforeEach = (guard: NavigationGuard) => router.beforeEach(guard)
  const beforeResolve = (guard: NavigationGuard) => router.beforeResolve(guard)
  const afterEach = (hook: (to: Route, from: Route) => any) => router.afterEach(hook)

  /**
   * 准备完成
   * @param cb
   * @param errorCb
   * @private
   */
  const onReady = (cb: VoidFunction, errorCb?: ErrorHandler) => router.onReady(cb, errorCb)

  /**
   * promise包装判断路由是否准备完成
   */
  const isReady = () =>
    new Promise<void>((resolve1, reject) => {
      onReady(resolve1, reject)
    })

  /**
   * 路由加载失败
   * @param errorCb
   */
  const onError = (errorCb: ErrorHandler) => router.onError(errorCb)

  /**
   * 获取所有 路由记录的完整列表。
   */
  const getRoutes = (): RouteRecordPublic[] => router.getRoutes()

  const getMatchedComponents = (to?: RawLocation | Route): Component[] =>
    router.getMatchedComponents(to)

  const addRoute = (parent: RouteConfig | string, route?: RouteConfig) =>
    router.addRoute(parent, route)

  const addRoutes = (routes: RouteConfig[]) => router.addRoutes(routes)

  return {
    currentRoute,
    push,
    replace,
    go,
    back,
    forward,
    match,
    resolve,
    hasRoute,
    onReady,
    isReady,
    onError,
    getRoutes,
    beforeEach,
    beforeResolve,
    afterEach,
    getMatchedComponents,
    addRoutes,
    addRoute,
  }
}
